#pragma once

#include <unordered_map>
#include <pthread.h>
#include "Util.hpp"
#include "Config.hpp"

namespace cloud
{
    // 服务端需要管理的备份文件相关的数据
    struct BackupInfo
    {
        bool pack_flag;        // 文件是否被压缩标志位
        size_t file_size;      // 文件大小
        time_t visittime;      // 最后一次访问时间
        time_t Modifytime;     // 最后一次修改时间
        std::string real_path; // 文件实际在服务器上存储路径名称
        std::string pack_path; // 压缩包存储路径名称
        std::string url_path;  // 资源请求路径名称

        // 调用该函数可以自动帮助新的 BackupInfo 结构进行数据填充
        bool NewBackupInfo(const std::string &realpath)
        {
            FileUtil fu(realpath);
            if (!fu.Exists())
            {
                std::cout << "newbackupinfo failed : file not exits" << std::endl;
                return false;
            }

            std::string filename = fu.FileName();
            Config *config = Config::GetInstance();
            std::string packdir = config->GetPackDir();
            std::string packsuffix = config->GetPackFileSuffix();
            std::string downloadPrefix = config->GetDownloadPrefix();

            this->pack_flag = false;
            this->file_size = fu.FileSize();
            this->visittime = fu.LastVisitTime();
            this->Modifytime = fu.LastModifyTime();
            this->real_path = realpath;
            this->pack_path = packdir + filename + packsuffix;
            this->url_path = downloadPrefix + filename;
            return true;
        }
    };

    // 备份文件数据管理结构
    class DataManager
    {
    private:
        std::string backup_file_;                           // 备份文件数据管理信息BackupInfo，持久化存储文件
        std::unordered_map<std::string, BackupInfo> table_; // BackupInfo 内存存储，方便查询 <url, BackInfo>
        pthread_rwlock_t rwlock_;                           // 读写锁，读共享，写互斥
    public:
        DataManager()
        {
            backup_file_ = Config::GetInstance()->GetBackFileData();
            pthread_rwlock_init(&rwlock_, nullptr);
            InitLoad();
        }

        ~DataManager()
        {
            pthread_rwlock_destroy(&rwlock_);
        }

        // 插入新数据
        bool Insert(const BackupInfo &info)
        {
            pthread_rwlock_wrlock(&rwlock_);
            table_[info.url_path] = info;
            pthread_rwlock_unlock(&rwlock_);
            Storage(); // 持久化

            return true;
        }

        // 修改更新
        bool Update(const BackupInfo &info)
        {
            pthread_rwlock_wrlock(&rwlock_);
            table_[info.url_path] = info;
            pthread_rwlock_unlock(&rwlock_);
            Storage(); // 持久化

            return true;
        }

        // 通过文件的URL资源路径获取文件信息
        bool GetOneByURL(const std::string &url, BackupInfo *info)
        {
            pthread_rwlock_wrlock(&rwlock_);
            auto it = table_.find(url);
            if (it == table_.end())
            {
                pthread_rwlock_unlock(&rwlock_);
                return false;
            }

            *info = it->second;
            pthread_rwlock_unlock(&rwlock_);
            return true;
        }

        // 通过文件的真是存储路径获取文件信息
        bool GetOneByRealpath(const std::string &realpath, BackupInfo *info)
        {
            pthread_rwlock_wrlock(&rwlock_);
            auto it = table_.begin();
            for (; it != table_.end(); ++it)
            {
                if (it->second.real_path == realpath)
                {
                    *info = it->second;
                    pthread_rwlock_unlock(&rwlock_);
                    return true;
                }
            }

            pthread_rwlock_unlock(&rwlock_);
            return false;
        }

        // 获取所有文件的信息
        bool GetAll(std::vector<BackupInfo> *arry)
        {
            pthread_rwlock_wrlock(&rwlock_);
            auto it = table_.begin();
            for (; it != table_.end(); ++it)
            {
                arry->push_back(it->second);
            }
            pthread_rwlock_unlock(&rwlock_);

            return true;
        }

        // 持久化存储管理数据
        bool Storage()
        {
            // 1.获取所有数据
            std::vector<BackupInfo> arry;
            this->GetAll(&arry);
            // 2.将数据添加到json::value中
            Json::Value root;
            for (int i = 0; i < arry.size(); ++i)
            {
                Json::Value tmp;
                tmp["pack_flag"] = arry[i].pack_flag;
                tmp["file_size"] = (Json::Int64)arry[i].file_size;
                tmp["Modifytime"] = (Json::Int64)arry[i].Modifytime;
                tmp["visittime"] = (Json::Int64)arry[i].visittime;
                tmp["pack_path"] = arry[i].pack_path;
                tmp["real_path"] = arry[i].real_path;
                tmp["url_path"] = arry[i].url_path;
                root.append(tmp);
            }
            // 3.将root中的数据进行序列化
            std::string content;
            if (!JsonUtil::Serialize(root, &content))
            {
                std::cout << "storage Serialize root failed !" << std::endl;
                return false;
            }
            // 4.将序列化后的内容写入 backup_file_ 持久化文件中
            FileUtil fu(backup_file_);
            fu.SetContent(content);
            return true;
        }

        // 系统重启时，初始化加载数据
        bool InitLoad()
        {
            // 1. 获取持久化管理数据, 在 backup_file_ 中
            std::string content;
            FileUtil fu(backup_file_);
            if (!fu.Exists())
            {
                return true;
            }
            fu.GetContent(&content);
            // 2. 将数据反序列化
            Json::Value root;
            JsonUtil::UnSerialize(content, &root);
            // 3. 将反序列化得到的Json::Value中的数据添加到table_中
            for (int i = 0; i < root.size(); ++i)
            {
                BackupInfo info;
                info.file_size = root[i]["file_size"].asInt64();
                info.Modifytime = root[i]["Modifytime"].asInt64();
                info.visittime = root[i]["visittime"].asInt64();
                info.pack_flag = root[i]["pack_flag"].asBool();
                info.pack_path = root[i]["pack_path"].asString();
                info.real_path = root[i]["real_path"].asString();
                info.url_path = root[i]["utl_path"].asString();
                Insert(info);
            }

            return true;
        }
    };
}